package com.opennews.openplatform

import grails.compiler.GrailsCompileStatic
import grails.gorm.transactions.Transactional
import groovy.transform.CompileDynamic
import org.hibernate.criterion.CriteriaSpecification
import org.hibernate.sql.JoinType

@Transactional(readOnly = true)
@GrailsCompileStatic
class JourneyService {
    RegionService regionService

    /**
     * Queries all journeys which associated with provided account group.
     * @return The list of map instance which contains keys below.
     *          id: String of the journey id.
     *          startDate: Start date of the journey.
     *          endDate: End date of the journey.
     *          regionId: String of the region id.
     *          regionName: String of the name of the region.
     *          regionTitle: String of the title of the region.
     *          regionFlagUrl: String of the flag url of the region.
     */
    @CompileDynamic
    List<Map<String, ?>> queryListByAccountGroup(String accountGroupId, int pageSize, int pageIndex) {
        // Queries detail by account group id.
        return Journey.createCriteria().list {
            // Transforms the result data format to Map instance.
            resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)

            // Sets the join type for region property in order to get its id and title.
            createAlias("region", "region", JoinType.INNER_JOIN)

            // Sets the query condition.
            eq("accountGroup.id", accountGroupId)

            // Specifies the property we need.
            projections {
                property("id", "id")
                property("startDate", "startDate")
                property("endDate", "endDate")
                property("region.id", "regionId")
                property("region.name", "regionName")
                property("region.title", "regionTitle")
            }

            // Sets the orderby.
            order("startDate", "desc")

            // How many rows are return for this trip.
            maxResults(pageSize)

            // Where to take the rows.
            firstResult(pageSize * pageIndex)
        }.each { Map<String, ?> item ->
            item.regionFlagUrl = regionService.getFlagUrl(item.regionName.toString())
        } as List<Map<String, ?>>
    }

    /**
     * Adds new journey for account group.
     * @param accountGroupId: The string of user account group id.
     * @param data: The map which contains keys with same name as journey properties.
     */
    @Transactional
    @CompileDynamic
    void add(String accountGroupId, Map<String, ?> data) {
        def postedregion = data.region as Map<String, ?>

        // Creates new Journey with posted data.
        new Journey().with {
            accountGroup = AccountGroup.proxy(accountGroupId)
            region = Region.proxy(postedregion.id.toString())
            startDate = DateHelper.parseFromLongDateTime(data.startDate)
            endDate = DateHelper.parseFromLongDateTime(data.endDate)
            save()
        }
    }

    /**
     * Updates journey info based on provided map data and its id.
     * The map keys must match journey property names.
     * If the map item will be ignored if its value is null.
     * @param accountGroupId: The string of user account group id.
     * @param data: The map which contains keys with same name as journey properties.
     * The map must contains key of id.
     */
    @Transactional
    @CompileDynamic
    void update(String accountGroupId, Map<String, ?> data) {
        // Removing id to prevent update error.
        def id = data.id.toString()
        data.remove("id")

        // Sets proxy region to prevent creating new region.
        def regionData = data.region as Map<String, ?>
        data.region = Region.proxy(regionData.id.toString())

        // Converts the string to date.
        data.startDate = DateHelper.parseFromLongDateTime(data.startDate.toString())
        data.endDate = DateHelper.parseFromLongDateTime(data.endDate.toString())

        Journey.where {
            id == id &&
                accountGroup == AccountGroup.proxy(accountGroupId)
        }.updateAll(data)
    }

    /**
     * Deletes the Journey with provided id.
     * @param accountGroupId: The string of the user account group id.
     * @param inputId: The journey id.
     */
    @Transactional
    @CompileDynamic
    boolean delete(String accountGroupId, String inputId) {
        def isDeleted = false

        // Queries the input id and makes sure it belongs to the specific account group.
        // This is an example shows how to secure the data by taking extra action.
        // This can void other account group users call api directly and provide id which they don't actually own it.
        // AKA parallel authentication.
        // Checks if this journey contains any income/expense.
        if (JourneyIncomeExpenseDetail.countByAccountGroupAndJourney(AccountGroup.proxy(accountGroupId), Journey.proxy(inputId)) == 0) {
            // If this journey contains no income/expense then deletes it.
            Journey.where {
                accountGroup == AccountGroup.proxy(accountGroupId) &&
                    id == inputId
            }.deleteAll()

            isDeleted = true
        }

        return isDeleted
    }
}
